本文在实践一的基础上,通过例子讲述如何根据 axios
库构建前端需要的请求函数。
前后端交互的基础架构如图(window
会监听响应出错事件):

将后端返回的响应数据格式简单地设定为:
1 2 3 4 5
| { "code": 0, "error": "错误信息", "data": "响应数据" }
|
响应字段含义如下表:
name |
type |
description |
code |
number |
响应码, 0 代表正常,1 代表异常 |
error |
string / null |
响应错误信息,code=0 时为 null |
data |
any |
响应数据,code=1 时为 null |
定义了响应数据格式后,可以通过 TypeScript
对其进一步抽象:
1 2 3 4 5 6 7
| interface IResponse<T> { code: number error: string | null data: T }
|
由于不同的后端接口返回的响应 data
数据格式不一致,因此需要开发者在使用 IResponse
的时候具体指定 T
。
通常,服务端接口的返回值带有一定的业务属性,这些属性代表着特定的业务含义,由这些业务属性组成的数据结构可以代表某类具体的业务实体。我们可以通过 TypeScript
来对这些业务实体进行抽象,举个例子:
现有一接口 /user/detail
会根据请求参数返回某个用户的具体信息,响应数据格式如下:
1 2 3 4 5 6 7 8 9
| { "code": 0, "error": null, "data": { "name": "user name", "age": 20, "department": "tech" } }
|
name |
type |
description |
name |
string |
用户姓名 |
age |
number |
用户年龄 |
department |
string |
用户所属部门 |
/user/detail
返回某个用户的具体信息,包括姓名、年龄和所属部门,根据这些业务属性,可以创建一个针对用户实体模型的抽象接口,后续所有的 IUser
实例都应包含以上三个属性。
1 2 3 4 5 6 7
| interface IUser { name: string age: number department: string }
|
对于某个请求,在定义了服务端响应的数据结构和响应的实体模型后,还需再定义对应的请求参数。还是以 /user/detail
接口为例,它可以根据某个具体的姓名来查找对应的用户信息,可以定义它的请求参数接口为:
1 2 3 4 5 6 7 8
| interface IGetUserReq { name: string } type IGetUserRes = IUser | null
|
前面我们规定了前后端交互的数据格式,这里我们将定义异常响应数据带来的错误 IHttpError
,它在继承前端基础的异常接口 Error
的前提下,额外新增了 name
和 code
两个属性:
1 2 3 4 5 6
| interface IHttpError extends Error { name: string code: number }
|
name |
type |
description |
name |
string |
IHttpError 的名称 |
code |
number |
对应响应的 code |
在通过 axios
定义请求函数的过程中, IHttpError
类型的错误会通过 Promise.reject(error)
的形式抛出,而全局 window
对象会监听未被处理的 Promise.reject
异常事件 unhandledrejection
。
1 2 3 4 5 6 7 8 9 10 11 12 13
| window.addEventListener('unhandledrejection', function(e: IUnhandledRejection<IHttpError>) { if (e.detail) { const { reason } = e.detail if (reason && reason.name === 'HttpError') { e.preventDefault() message.error(reason.message) } } })
|
1 2 3 4 5 6 7 8 9 10 11
| interface IUnhandledRejectionDetail<T> { promise: Promise<any> reason: T } interface IUnhandledRejection<T> extends Event { detail: IUnhandledRejectionDetail<T> }
|
这里只对 axios
库进行简单封装,要求:
- 请求基础
url
为 http://localhost:8080/api
- 请求头通过
x-requested-with
字段标识是异步请求
- 如果返回的结果出错,则自动抛出异常
针对 axios
相关的类型定义,可以戳这里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import axios, {AxiosResponse, AxiosInstance} from 'axios' function rejectHttpError(message: string, code: string | number = null) { const error:IHttpError = new Error(message) error.name = 'HttpError' if (code != null) { error.code = code } return Promise.reject(error) } const instance: AxiosInstance = axios.create({ baseURL: 'http://localhost:8080/api/', headers: { 'x-requested-with': 'XMLHttpRequest' } }) instance.interceptors.response.use( function(response: AxiosResponse<IResponse<any>>) { let result:IResponse<any> = response.data if (result.code !== 0) { return rejectHttpError(result.error || '请求异常!', result.code) } return response }) export default instance
|
到目前为止,还差最后一步,就是通过 axios
定义某个具体的请求函数,由于异常数据已被我们封装的 axios
内部自动处理,对于请求函数,我们只需关心请求路径、请求参数和响应返回的 data
即可。还是以 /user/detail
接口为例,请求参数类型为 IGetUserReq
,响应返回的 data
类型为 IGetUserRes
。
1 2 3 4 5 6
| import axios from './axios' import { AxiosResponse } from 'axios' export const getUserDetail = (params: IGetUserReq) => axios.get('/user/detail', {params}).then((res: AxiosResponse<IResponse<IGetUserRes>>) => res.data.data)
|
大部分情况下,对于正常的响应我们只需关心 res.data.data
,因此我们可以抽象一个 getResData
函数专门来获取 res.data.data
,请求函数定义就变为了
1 2 3 4 5 6 7 8 9 10 11
| import axios from './axios' import { AxiosResponse, AxiosPromise } from 'axios' function getResData<T>(promise: AxiosPromise<IResponse<T>>) { return promise.then((res: AxiosResponse<IResponse<T>>) => res.data.data) } export const getUserDetail = (params: IGetUserReq) => getResData<IGetUserRes>(axios.get('/user/detail', {params}))
|